package com.html2rss.ui.wizard;

import com.html2rss.core.parser.HTMLFeedExtracter;
import com.html2rss.core.exceptions.HtmlToRssException;
import com.html2rss.core.exceptions.SystemException;
import com.html2rss.core.HttpURL;
import com.html2rss.core.DomHelper;
import com.html2rss.models.UserPage;
import com.html2rss.models.WizardHistory;
import com.html2rss.plugins.InitializationPlugIn;
import com.dappit.Dapper.parser.ParserInitializationException;
import com.sun.tools.javac.util.Pair;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.util.EncodingUtil;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.w3c.dom.*;
import org.dom4j.DocumentException;

import javax.servlet.http.HttpServletRequest;
import javax.xml.xpath.XPathExpressionException;
import java.io.IOException;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.List;


/**
 * User: vvs
 * Date: 07.04.2007
 * Time: 1:59:23
 */
public class NavigateToHTMLPageAction extends BaseWizardAction {

    public ActionForward execute(ActionMapping actionMapping,
                                 ActionForm actionForm,
                                 javax.servlet.http.HttpServletRequest httpServletRequest,
                                 javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
        WizardHistory wh = getWizardNavigationHistory(httpServletRequest);
        UserPage userPage = requestPage(httpServletRequest);
        Document doc = parseAndFilterPage(userPage);
        addTagIds(doc);
        wh.addPage(userPage.getUrl(), doc);

        // send models to view
        userPage.setContent(HTMLFeedExtracter.saveXMLDocumentToString(doc));
        httpServletRequest.setAttribute("userPage", userPage);
        return actionMapping.findForward("success");
    }

    private void addTagIds(Document doc) throws SystemException {
        try {
            List<Node> result = DomHelper.getXPathNodeList("//*", doc);
            for (int i = 0; i < result.size(); i++) {
                Node node = result.get(i);
                Attr attr = doc.createAttribute(HTML_TO_RSS_ID);
                attr.setValue(String.valueOf(i));
                node.getAttributes().setNamedItem(attr);
            }
        } catch (XPathExpressionException e) {
            throw new SystemException();
        }
    }


    private String getDestUrl(javax.servlet.http.HttpServletRequest httpServletRequest) {
        return httpServletRequest.getParameter(URL_PARAM_NAME);
    }

    protected Document parseAndFilterPage(UserPage userPage) throws DocumentException, ParserInitializationException, HtmlToRssException, URIException {
        Document doc = InitializationPlugIn.getParser().parse(userPage.getContent());
        HTMLFeedExtracter.saveXMLDocument("testout.xml", doc);
        filterLinks(doc, userPage.getUrl());
        return doc;
    }

    protected UserPage requestPage(javax.servlet.http.HttpServletRequest httpServletRequest) throws IOException, HtmlToRssException {
        UserPage userPage = new UserPage();
        HttpClient client = new HttpClient();
        HttpMethodBase method = null;
        HttpURL destUrl = new HttpURL(getDestUrl(httpServletRequest));
        for (Pair<String, String>param: destUrl.getQueryParams()) {
            userPage.getParameters().add(new Pair<String, String>(param.fst, param.snd));
        }
        if ("GET".equals(httpServletRequest.getMethod())) {
            method = new GetMethod();
            for (Object par : httpServletRequest.getParameterMap().keySet()) {
                if (!URL_PARAM_NAME.equals(par)) {
                    String a = (String) par;
                    String b = httpServletRequest.getParameter((String) par);
                    destUrl.addQueryParameter(a, b);
                    userPage.getParameters().add(new Pair<String, String>(a,b));
                }
            }
        } else if ("POST".equals(httpServletRequest.getMethod())) {
            PostMethod postMethod = new PostMethod();
            for (Object param : httpServletRequest.getParameterMap().keySet()) {
                if (!URL_PARAM_NAME.equals(param)) {
                    String a = (String) param;
                    String b = httpServletRequest.getParameter(a);
                    postMethod.addParameter(a, b);
                    userPage.getParameters().add(new Pair<String, String>(a,b));
                }
            }
            method = postMethod;
        } else {
            throw new HtmlToRssException("Unknown HTTP method!");
        }
        method.setURI(new URI(destUrl.getURI()));
        method.setFollowRedirects(true);
        client.executeMethod(method);
        httpServletRequest.setAttribute("requestMethod", method);


        String charset = extractEncoding(method);
        if (charset == null) {
            String html = method.getResponseBodyAsString();
            charset = extractEncoding(html);
        }

        String html = EncodingUtil.getString(method.getResponseBody(),charset) ;
        userPage.setContent(html);
        userPage.setUrl(new HttpURL(method.getURI().getURI()));
        return userPage;
    }

    private String extractEncoding(HttpMethodBase method) {
        try {
            return method.getResponseHeader("Content-Type").getElements()[0].getParameterByName("charset").getValue();
        } catch (Exception e) {
            return null;
        }
    }

    private String extractEncoding(String html) {
        Pattern p = Pattern.compile("(?smi)META\\s+http-equiv=\"content-type\"\\s+content=\"text/html;\\s+charset=([\\w-_]+)\">");
        Matcher m = p.matcher(html);
        while (m.find()) {
            if (m.groupCount() == 1) {
                return m.group(1);
            }
        }
        return null;
    }

    private void filterLinks(Document doc, HttpURL baseUrl) throws HtmlToRssException, URIException {
        String[] tagNames = {
                "a"
        };
        String[] attributes = {
                "href"
        };
        for (String tag : tagNames) {
            NodeList nl = doc.getElementsByTagName(tag);
            for (int i = 0; i < nl.getLength(); i++) {
                try {
                    Node n = nl.item(i);
                    NamedNodeMap namedNodeMap = n.getAttributes();
                    for (int k = 0; k < attributes.length; k++) {
                        Node nod = namedNodeMap.getNamedItem(attributes[k]);
                        if (nod != null) {
                            nod.setNodeValue(applyFilterToLink(nod.getNodeValue(), baseUrl));
                        }
                    }
                } catch (URIException e) {
                    // problem converting link
                    e.printStackTrace();
                }
            }
        }
        NodeList nl = doc.getElementsByTagName("form");
        for (int i = 0; i < nl.getLength(); i++) {
            try {
                Node formNode = nl.item(i);
                Node actionAttr = formNode.getAttributes().getNamedItem("action");
                String url = actionAttr.getNodeValue();
                actionAttr.setNodeValue(getActionUrl());
                Element hiddenUrl = doc.createElement("input");
                hiddenUrl.setAttribute("type", "hidden");
                hiddenUrl.setAttribute("name", URL_PARAM_NAME);
                hiddenUrl.setAttribute("value", getCannonicalPath(url, baseUrl));
                formNode.appendChild(hiddenUrl);
            } catch (URIException e) {
                // problem converting link
                e.printStackTrace();
            }
        }

        String[][] fullURLTags = {
                {"img", "src"},
                {"link", "href"},
                {"td", "background"},
                {"iframe", "src"},
                {"script", "src"},
        };

        for (String[] fullURLTag : fullURLTags) {
            nl = doc.getElementsByTagName(fullURLTag[0]);
            for (int i = 0; i < nl.getLength(); i++) {
                try {
                    Node node = nl.item(i);
                    for (int k = 1; k < fullURLTag.length; k++) {
                        Node attr = node.getAttributes().getNamedItem(fullURLTag[k]);
                        if (attr!=null) {
                            attr.setNodeValue(getCannonicalPath(attr.getNodeValue(), baseUrl));
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private String applyFilterToLink(String url, HttpURL baseUrl) throws HtmlToRssException, URIException {
        String start = getActionUrl() + "?" + URL_PARAM_NAME + "=";
        return start + getCannonicalPath(url, baseUrl);
    }

    private String getCannonicalPath(String url, HttpURL baseUrl) throws HtmlToRssException, URIException {
        if (url.indexOf("://")>0) {
            String start = url.substring(0, url.indexOf("://"));
            if (!start.equalsIgnoreCase("http")) return "#";
        }
        HttpURL httpURL = new HttpURL(url);
        if (httpURL.getHost() != null) {
            return url;
        } else if (url.startsWith("/")) {
            return baseUrl.getHostPort() + url;
        } else {
            return baseUrl.getHostPortHierPath() + url;
        }
    }

    private String getActionUrl() {
        return InitializationPlugIn.appHostName + "/wizard/navigate.do";
    }


}
